package me.minecraft.mod.resourcepackdemo.mixin;

import com.mojang.logging.LogUtils;
import me.minecraft.mod.resourcepackdemo.Main;
import net.minecraft.resource.InputSupplier;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.resource.ZipResourcePack;
import net.minecraft.util.Identifier;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author langle__
 * @version 1.0
 */
@Mixin(ZipResourcePack.class)
public abstract class ZipResourcePackMixin {

    @Unique
    private static final Logger LOGGER = LogUtils.getLogger();

    @Shadow
    protected abstract String appendOverlayPrefix(String path);

    @Inject(method = "findResources", at = @At("HEAD"), cancellable = true)
    private void findResources(ResourceType type, String namespace, String prefix, ResourcePack.ResultConsumer consumer, CallbackInfo ci) {
        ci.cancel();
        ZipFile zipFile = getZipFileViaReflection();
        if (zipFile == null) return;
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        String typeDirectory = type.getDirectory();
        String baseResourcePath = this.appendOverlayPrefix(typeDirectory + "/" + namespace + "/");
        String searchPath = baseResourcePath + prefix + "/";

        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            String entryName = zipEntry.getName();
            if (zipEntry.isDirectory()) continue;
            if (!entryName.startsWith(searchPath)) continue;
            String relativePath = entryName.substring(baseResourcePath.length());
            String tempRelativePath;
            boolean flag = true;
            if (relativePath.endsWith("\\")) {
                tempRelativePath = relativePath.substring(0, relativePath.length() - 1);
                flag = false;
            } else {
                tempRelativePath = relativePath;
            }
            Identifier identifier = Identifier.tryParse(namespace, tempRelativePath);
            if (identifier == null) {
                LOGGER.warn("Invalid path in datapack: {}:{}, ignoring", namespace, tempRelativePath);
                continue;
            }

            if (flag) {
                consumer.accept(identifier, InputSupplier.create(zipFile, zipEntry));
                continue;
            }

            InputSupplier<InputStream> supplier;
            try {
                InputStream inputStream = zipFile.getInputStream(zipEntry);
                InputStream input = Main.swapBytes(inputStream);
                supplier = () -> input;
            } catch (IOException e) {
                LOGGER.warn("Failed to open resource: {}", entryName, e);
                continue;
            }

            consumer.accept(identifier, supplier);
        }
    }

    @Inject(method = "openFile", at = @At("HEAD"), cancellable = true)
    private void openFile(String path, CallbackInfoReturnable<InputSupplier<InputStream>> cir) {
        cir.cancel();
        ZipFile zipFile = getZipFileViaReflection();
        if (zipFile == null) {
            cir.setReturnValue(null);
            return;
        }
        ZipEntry zipEntry = zipFile.getEntry(this.appendOverlayPrefix(path));
        if (zipEntry == null) {
            cir.setReturnValue(null);
            return;
        }
        String entryName = zipEntry.getName();
        if (!entryName.endsWith("\\")) {
            cir.setReturnValue(InputSupplier.create(zipFile, zipEntry));
            return;
        }
        try {
            InputStream inputStream = zipFile.getInputStream(zipEntry);
            cir.setReturnValue(() -> Main.swapBytes(inputStream));
        } catch (IOException e) {
            LOGGER.warn("Failed to open resource: {}", entryName, e);
        }
    }

    @Unique
    private ZipFile getZipFileViaReflection() {
        try {
            //noinspection JavaReflectionMemberAccess
            Field zipFileField = ZipResourcePack.class.getDeclaredField("field_45038");
            zipFileField.setAccessible(true);

            Object zipFileWrapper = zipFileField.get(this);
            if (zipFileWrapper == null) return null;

            Method openMethod = zipFileWrapper.getClass().getDeclaredMethod("method_52426");
            openMethod.setAccessible(true);
            return (ZipFile) openMethod.invoke(zipFileWrapper);
        } catch (Exception e) {
            LOGGER.error("Failed to access zip file via reflection", e);
        }
        return null;
    }

}
